package camera;

import ray.Ray;
import vector.Vector;

/**
 * This class is in charge of operation of camera. Furthermore, it will produce
 * necessary rays.
 * 
 * @author Yuting Wu
 * @author Kan Lin
 * @author Bing Dong
 * @version 1.0
 */
public class Camera {

	private Vector pLocation;
	private Vector pLookat;
	private Vector vUp;

	// the width and height of the picture in world coordinate
	private double picWidth;
	private double picHeight;

	// the width and height of the picture in pixels
	private int imgWidth;
	private int imgHeight;

	private double zoomValue;

	private Vector vCamDirection;
	private Vector vRightDirection;
	private Vector vDownDirection;
	private Vector vLeftUpCorner;

	private Vector pInPixels;
	private Ray desiredRay;

	

	/**
	 * This class is designed for testing. The only use is invoked by class
	 * ReadXMLTest
	 * 
	 * @param pLocation
	 *            the location of the camera
	 * @param pLookat
	 *            the point you turn the camera towards
	 * @param vUp
	 *            a vector giving the "up" direction of the picture
	 */
	public Camera(final Vector pLocation, final Vector pLookat, final Vector vUp) {
		super();
		this.pLocation = pLocation;
		this.pLookat = pLookat;
		this.vUp = vUp;
		this.picWidth = 1;
		this.picHeight = 1;
		this.imgWidth = 1;
		this.imgHeight = 1;
		this.zoomValue = 1;
	}

	/**
	 * Get a Vector calculated by camera location and the point camer towards
	 * shows the direction of camera
	 * 
	 * @return Vector vector for camera direction
	 */
	public Vector getCamDirection() {
		return this.vCamDirection;
	}

	/**
	 * Calculate the normalised direction vector of the camera
	 */
	private void calCamDirection() {
		this.vCamDirection = pLookat.subtract(this.pLocation).normalise();
	}

	/**
	 * Get a Vector calculate by a vector giving a "up" direction of picture and
	 * the direction of camera shows the right direction of picture
	 * 
	 * @return Vector
	 */
	public Vector getPicRightDirection() {
		return this.vRightDirection;
	}

	/**
	 * Compute the normalised right direction vector of the picture
	 */
	private void calPicRightDirection() {

		this.vRightDirection = vUp.crossProduct(this.vCamDirection).normalise();

	}

	/**
	 * Get a Vector calculate by a vector giving the right direction of picture
	 * and the direction of camera shows the down direction of picture
	 * 
	 * @return Vector
	 */
	public Vector getPicDownDirection() {
		return this.vDownDirection;
	}

	/**
	 * Compute the normalised down direction vector of the picture
	 */
	private void calPicDownDirection() {

		this.vDownDirection = this.vRightDirection.crossProduct(vCamDirection).normalise();
	}

	/**
	 * Get a Vector shows the left hand corner of picture in the world
	 * coordinates
	 * 
	 * @return Vector
	 */
	public Vector getLeftUpCorner() {
		return this.vLeftUpCorner;
	}

	/**
	 * Compute the normalised down direction vector of the picture
	 */
	private void calLeftUpCorner() {

		final Vector camZoom = this.vCamDirection.multiply(zoomValue);
		final Vector pRd = this.vRightDirection.multiply(this.picWidth / 2);
		final Vector pDd = this.vDownDirection.multiply(this.picHeight / 2);

		this.vLeftUpCorner = this.pLocation.add(camZoom).subtract(pRd).subtract(pDd);

	}

	/**
	 * Get the point which is in the middle of the sought pixel
	 * 
	 * @return
	 */
	public Vector getPointMiddlePixels() {
		return this.pInPixels;
	}

	/**
	 * Compute the point Px,y which is in the middle of the sought pixel
	 * 
	 * @param xPos
	 *            x coordinate of point
	 * @param yPos
	 *            y coordinate of point
	 */

	private void calPointMiddlePixels(final int xPos, final int yPos) {
		// 0<x<=picWidth
		final Vector vwr = this.vRightDirection.multiply((picWidth / imgWidth) * (xPos + 0.5));
		final Vector vdr = this.vDownDirection.multiply((picHeight / imgHeight) * (yPos + 0.5));
		this.pInPixels = this.vLeftUpCorner.add(vwr).add(vdr);
	}

	/**
	 * 
	 * @return the desired ray
	 */
	public Ray getDesiredRay() {
		return this.desiredRay;
	}

	/**
	 * calculate desired ray
	 */
	private void calDesiredRay() {
		final Vector vDesRay = pInPixels.subtract(this.pLocation).normalise();
		this.desiredRay = new Ray(this.pLocation, vDesRay);

	}

	/**
	 * a method which produces rays
	 * 
	 * @return produced rays
	 */
	public Ray[][] produceRay(final int picWidth,
			final int picHeight, final int imgWidth, final int imgHeight, final double zoomValue) {
		this.picWidth = picWidth;
		this.picHeight = picHeight;
		this.imgWidth = imgWidth;
		this.imgHeight = imgHeight;
		this.zoomValue = zoomValue;
		
		Ray[][] raylist = new Ray[imgHeight][imgWidth];
		this.calCamDirection();
		this.calPicRightDirection();
		this.calPicDownDirection();
		this.calLeftUpCorner();
		for (int i = 0; i < imgWidth; i++) {
			for (int j = 0; j < imgHeight; j++) {

				this.calPointMiddlePixels(i, j);
				this.calDesiredRay();
				raylist[j][i] = desiredRay;
			}
		}
		return raylist;
	}
	
	/**
	 * @return a string contains camera information
	 */
	public String toString() {
		return "Camera:Location: " + this.pLocation + ",Lookat: " + this.pLookat + ",Up: " + this.vUp;
	}
}
